package com.tianji.learning.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domin.dto.LearningRecordFormDTO;
import com.tianji.learning.domin.po.LearningLesson;
import com.tianji.learning.domin.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHadler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import javax.validation.constraints.NotNull;
import java.util.List;

import static com.tianji.learning.enums.LessonStatus.*;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author 金山
 * @since 2025-10-22
 */
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {
    private final ILearningLessonService lessonService;
    private final CourseClient courseClient;
    private final LearningRecordDelayTaskHadler taskHadler;

    /**
     * 查询当前用户指定课程的学习进度
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //1.根据userID 和 courseId 查询课表信息
        LearningLesson lesson = lessonService.lambdaQuery().eq(LearningLesson::getUserId, UserContext.getUser())
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if (ObjectUtil.isEmpty(lesson)) {
            return null;
        }

        //2.根据 lessonid 查询课表下的学习记录
        List<LearningRecord> lessons = this.lambdaQuery().eq(LearningRecord::getLessonId, lesson.getId()).list();
        //3.封装DTO并返回
        return LearningLessonDTO.of(lesson.getId(), lesson.getLatestSectionId(),
                BeanUtils.copyList(lessons, LearningRecordDTO.class));


    }

    /**
     * 提交学习记录
     *
     * @param formDTO
     */
    @Override
    @Transactional
    public void submitLearningRecord(LearningRecordFormDTO formDTO) {
        //判断是不是考试
        boolean firstFinished = formDTO.getSectionType() == SectionType.VIDEO ? handleExamRecord(formDTO) : handleVeidRecord(formDTO);

        if (firstFinished && formDTO.getDuration() == 1) {
            return;
        }
        //处理课表数据
        handleLearningLesson(formDTO, firstFinished);
    }

    /**
     * 处理考试的学习记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleVeidRecord(LearningRecordFormDTO formDTO) {
        //已经确定不是视频，所以是考试了
        //将dto转换成po
        LearningRecord record = BeanUtils.toBean(formDTO, LearningRecord.class);

        //填充缺少的数据
        record.setUserId(UserContext.getUser());
        record.setFinished(true);
        record.setFinishTime(formDTO.getCommitTime());

        //写入数据库
        boolean result = save(record);
        if (!result) {
            throw new DbException("保存学习记录失败");
        }

        return true;
    }

    /**
     * 处理视频的学习记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleExamRecord(LearningRecordFormDTO formDTO) {
        //1.查询旧学习记录
        LearningRecord oldRecord = queryOldRecord(formDTO.getLessonId(), formDTO.getSectionId());


        //2.判断学习记录是否存在
        if (ObjectUtil.isEmpty(oldRecord)) {
            //3.如果不存在，新增学习记录
            LearningRecord record = BeanUtils.toBean(formDTO, LearningRecord.class);
            record.setUserId(UserContext.getUser());

            boolean result = save(record);
            if (!result) {
                throw new DbException("保存视频学习记录失败");
            }
            return false;
        }

        //4.如果存在，更新学习记录
        //4.1判断是否是第一次完成
        boolean firstFinised = !oldRecord.getFinished() && formDTO.getMoment() << 1 >= formDTO.getDuration();

        //4.2如果是非第一次学完，将学习记录暂存到redis，并且提交一个延时任务
        if (!firstFinised) {
            LearningRecord learningRecord = new LearningRecord();
            learningRecord.setLessonId(formDTO.getLessonId());
            learningRecord.setSectionId(formDTO.getSectionId());
            learningRecord.setId(oldRecord.getId());
            learningRecord.setMoment(formDTO.getMoment());
            learningRecord.setFinished(oldRecord.getFinished());
            taskHadler.addLearningRecordAndDelayCheckTask(learningRecord);
            formDTO.setDuration(-1);
            return false;
        }


        //4.3如果是第一次学完，更新学习记录
        boolean update = this.lambdaUpdate()
                .eq(LearningRecord::getId, oldRecord.getId())
                .set(LearningRecord::getMoment, formDTO.getMoment())
                .set(LearningRecord::getFinished, firstFinised)
                .set(LearningRecord::getFinishTime, formDTO.getCommitTime())
                .update();

        if (!update) {
            throw new DbException("更新视频学习记录失败");
        }

        //4.4 清理redis缓存中的学习记录（目的：保证redis和mysql数据双写一致性）
        taskHadler.cleanLearningRecordCache(formDTO.getLessonId(), formDTO.getSectionId());
        //5.返回是否是第一次学完的标识
        return firstFinised;
    }

    /**
     * 查询旧学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    private LearningRecord queryOldRecord(@NotNull(message = "课表id不能为空") Long lessonId, @NotNull(message = "节的id不能为空") Long sectionId) {
        //0.先从缓存中查询学习记录
        LearningRecord oldRecord = taskHadler.readLearningRecordFromCache(lessonId, sectionId);
        //2.判断缓存中是否存在数据，如果存在，直接返回
        if (ObjectUtil.isEmpty(oldRecord)) {
            return oldRecord;
        }
        //3.如果不在缓存中，从数据库中查询, 并将结果放入缓存中
        //3.1.查询旧的学习记录
        oldRecord = lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .one();

        if (ObjectUtil.isEmpty(oldRecord)) {
            return null;
        }

        //不为空，到这步redis缓存是没有的，从mysql中找到的，要重新写入redis
        taskHadler.writeLearningRecordToCache(oldRecord);
        //返回数据
        return oldRecord;
    }

    /**
     * 处理课表数据
     *
     * @param formDTO
     * @param firstFinished
     */
    private void handleLearningLesson(LearningRecordFormDTO formDTO, boolean firstFinished) {
        //查询课表数据（已学习小节数)
        LearningLesson lesson = this.lessonService.getById(formDTO.getLessonId());
        AssertUtils.isNotNull(lesson, "查询课表数据失败");

        //2.查询全部小节信息
        boolean allLearnd = false;
        if (firstFinished) {
            //2.1查询课程数据（总小节数）
            CourseFullInfoDTO courseInfoById = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            AssertUtils.isNotNull(lesson, "查询课表数据失败");
            //2.2 比较（已经完成的小节数）
            allLearnd = lesson.getLearnedSections() + 1 >= courseInfoById.getSectionNum();
        }

        //更新课表状态为已经学完

        this.lessonService.lambdaUpdate()
                .set(firstFinished, LearningLesson::getLearnedSections, lesson.getLearnedSections() + 1)
                .set(LearningLesson::getLatestSectionId, formDTO.getSectionId())
                .set(LearningLesson::getLatestLearnTime, formDTO.getCommitTime())
                .set(allLearnd, LearningLesson::getStatus, FINISHED)
                .set(lesson.getStatus() == NOT_BEGIN, LearningLesson::getStatus, LEARNING)
                .eq(LearningLesson::getId, formDTO.getLessonId())
                .update();

    }
}
